/*
 * This file is part of Cadmium.
 * Copyright (C) 2007-2010 Xavier Clerc.
 *
 * Cadmium is free software; you can redistribute it and/or modify
 * it under the terms of the GNU Lesser General Public License as published by
 * the Free Software Foundation; either version 3 of the License, or
 * (at your option) any later version.
 *
 * Cadmium is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */

package fr.x9c.cadmium.primitives.stdlib;

import fr.x9c.cadmium.kernel.Block;
import fr.x9c.cadmium.kernel.CodeRunner;
import fr.x9c.cadmium.kernel.Fail;
import fr.x9c.cadmium.kernel.Primitive;
import fr.x9c.cadmium.kernel.PrimitiveProvider;
import fr.x9c.cadmium.kernel.Value;

/**
 * Implements all primitives from 'lexing.c'.
 *
 * @author <a href="mailto:cadmium@x9c.fr">Xavier Clerc</a>
 * @version 1.0
 * @since 1.0
 */
@PrimitiveProvider
public final class Lexing {

    /** Index of <tt>REFILL_BUFF</tt> field in <tt>lexer_buffer</tt>. */
    private static final int REFILL_BUFF = 0;

    /** Index of <tt>LEX_BUFFER</tt> field in <tt>lexer_buffer</tt>. */
    private static final int LEX_BUFFER = 1;

    /** Index of <tt>LEX_BUFFER_LEN</tt> field in <tt>lexer_buffer</tt>. */
    private static final int LEX_BUFFER_LEN = 2;

    /** Index of <tt>LEX_ABS_POS</tt> field in <tt>lexer_buffer</tt>. */
    private static final int LEX_ABS_POS = 3;

    /** Index of <tt>LEX_START_POS</tt> field in <tt>lexer_buffer</tt>. */
    private static final int LEX_START_POS = 4;

    /** Index of <tt>LEX_CURR_POS</tt> field in <tt>lexer_buffer</tt>. */
    private static final int LEX_CURR_POS = 5;

    /** Index of <tt>LEX_LAST_POS</tt> field in <tt>lexer_buffer</tt>. */
    private static final int LEX_LAST_POS = 6;

    /** Index of <tt>LEX_LAST_ACTION</tt> field in <tt>lexer_buffer</tt>. */
    private static final int LEX_LAST_ACTION = 7;

    /** Index of <tt>LEX_EOF_REACHED</tt> field in <tt>lexer_buffer</tt>. */
    private static final int LEX_EOF_REACHED = 8;

    /** Index of <tt>LEX_MEM</tt> field in <tt>lexer_buffer</tt>. */
    private static final int LEX_MEM = 9;

    /** Index of <tt>LEX_START_P</tt> field in <tt>lexer_buffer</tt>. */
    private static final int LEX_START_P = 10;

    /** Index of <tt>LEX_CURR_P</tt> field in <tt>lexer_buffer</tt>. */
    private static final int LEX_CURR_P = 11;

    /** Index of <tt>LEX_BASE</tt> field in <tt>lexing_table</tt>. */
    private static final int LEX_BASE = 0;

    /** Index of <tt>LEX_BACKTRK</tt> field in <tt>lexing_table</tt>. */
    private static final int LEX_BACKTRK = 1;

    /** Index of <tt>LEX_DEFAULT</tt> field in <tt>lexing_table</tt>. */
    private static final int LEX_DEFAULT = 2;

    /** Index of <tt>LEX_TRANS</tt> field in <tt>lexing_table</tt>. */
    private static final int LEX_TRANS = 3;

    /** Index of <tt>LEX_CHECK</tt> field in <tt>lexing_table</tt>. */
    private static final int LEX_CHECK = 4;

    /** Index of <tt>LEX_BASE_CODE</tt> field in <tt>lexing_table</tt>. */
    private static final int LEX_BASE_CODE = 5;

    /** Index of <tt>LEX_BACKTRK_CODE</tt> field in <tt>lexing_table</tt>. */
    private static final int LEX_BACKTRK_CODE = 6;

    /** Index of <tt>LEX_DEFAULT_CODE</tt> field in <tt>lexing_table</tt>. */
    private static final int LEX_DEFAULT_CODE = 7;

    /** Index of <tt>LEX_TRANS_CODE</tt> field in <tt>lexing_table</tt>. */
    private static final int LEX_TRANS_CODE = 8;

    /** Index of <tt>LEX_CHECK_CODE</tt> field in <tt>lexing_table</tt>. */
    private static final int LEX_CHECK_CODE = 9;

    /** Index of <tt>LEX_CODE</tt> field in <tt>lexing_table</tt>. */
    private static final int LEX_CODE = 10;

    /** Constant indicating that no character is selected. */
    private static final int NO_CHAR = 256;

    /** Constant indicating that there is no destination. */
    private static final int NO_DEST = 0xFF;

    /** Constant indicating that there is no source. */
    private static final int NO_SRC = 0xFF;

    /**
     * No instance of this class.
     */
    private Lexing() {
    } // end empty constructor

    /**
     * Runs the lexing engine.
     * @param ctxt context
     * @param lextable lexing table
     * @param start current state
     * @param lexbuf lexing buffer
     * @return state evolution
     * @throws Fail.Exception if token is empty
     */
    @Primitive
    public static Value caml_lex_engine(final CodeRunner ctxt,
                                        final Value lextable,
                                        final Value start,
                                        final Value lexbuf)
        throws Fail.Exception {
        int state = start.asLong();
        final Block buff = lexbuf.asBlock();
        final Block table = lextable.asBlock();
        if (state >= 0) {
            buff.set(Lexing.LEX_LAST_POS, buff.get(Lexing.LEX_CURR_POS));
            buff.set(Lexing.LEX_START_POS, buff.get(Lexing.LEX_CURR_POS));
            buff.set(Lexing.LEX_LAST_ACTION, Value.MINUS_ONE);
        } else {
            state = -state - 1;
        } // end if/else
        while (true) {
            final int c;
            final int base = getShort(table.get(Lexing.LEX_BASE), state);
            if (base < 0) {
                return Value.createFromLong(-base - 1);
            } // end if
            final int backtrk = getShort(table.get(Lexing.LEX_BACKTRK), state);
            if (backtrk >= 0) {
                buff.set(Lexing.LEX_LAST_POS, buff.get(Lexing.LEX_CURR_POS));
                buff.set(Lexing.LEX_LAST_ACTION, Value.createFromLong(backtrk));
            } // end if
            if (buff.get(Lexing.LEX_CURR_POS).asLong() >= buff.get(Lexing.LEX_BUFFER_LEN).asLong()) {
                if (buff.get(Lexing.LEX_EOF_REACHED) == Value.FALSE) {
                    return Value.createFromLong(-state - 1);
                } else {
                    c = Lexing.NO_CHAR;
                } // end if/else
            } else {
                final int idx = buff.get(Lexing.LEX_CURR_POS).asLong();
                c = buff.get(Lexing.LEX_BUFFER).asBlock().getUnsignedByte(idx);
                buff.set(Lexing.LEX_CURR_POS, Value.createFromLong(idx + 1));
            } // end if/else
            if (getShort(table.get(Lexing.LEX_CHECK), base + c) == state) {
                state = getShort(table.get(Lexing.LEX_TRANS), base + c);
            } else {
                state = getShort(table.get(Lexing.LEX_DEFAULT), state);
            } // end if/else
            if (state < 0) {
                buff.set(Lexing.LEX_CURR_POS, buff.get(Lexing.LEX_LAST_POS));
                if (buff.get(Lexing.LEX_LAST_ACTION) == Value.MINUS_ONE) {
                    Fail.failWith("lexing: empty token");
                } else {
                    return buff.get(Lexing.LEX_LAST_ACTION);
                } // end if/else
            } else {
                if (c == Lexing.NO_CHAR) {
                    buff.set(Lexing.LEX_EOF_REACHED, Value.FALSE);
                } // end if
            } // end if/else
        } // end while
    } // end method 'caml_lex_engine(CodeRunner, Value, Value, Value)'

    /**
     * Runs the lexing engine.
     * @param ctxt context
     * @param lextable lexing table
     * @param start current state
     * @param lexbuf lexing buffer
     * @return state evolution
     * @throws Fail.Exception if token is empty
     */
    @Primitive
    public static Value caml_new_lex_engine(final CodeRunner ctxt,
                                            final Value lextable,
                                            final Value start,
                                            final Value lexbuf)
        throws Fail.Exception {
        int state = start.asLong();
        final Block buff = lexbuf.asBlock();
        final Block table = lextable.asBlock();
        if (state >= 0) {
            buff.set(Lexing.LEX_LAST_POS, buff.get(Lexing.LEX_CURR_POS));
            buff.set(Lexing.LEX_START_POS, buff.get(Lexing.LEX_CURR_POS));
            buff.set(Lexing.LEX_LAST_ACTION, Value.MINUS_ONE);
        } else {
            state = -state - 1;
        } // end if/else
        while (true) {
            final int c;
            final int base = getShort(table.get(Lexing.LEX_BASE), state);
            if (base < 0) {
                final int pcOff = getShort(table.get(Lexing.LEX_BASE_CODE), state);
                runTag(table.get(Lexing.LEX_CODE).asBlock(),
                       pcOff,
                       buff.get(Lexing.LEX_MEM).asBlock());
                return Value.createFromLong(-base - 1);
            } // end if
            final int backtrk = getShort(table.get(Lexing.LEX_BACKTRK), state);
            if (backtrk >= 0) {
                final int pcOff = getShort(table.get(Lexing.LEX_BACKTRK_CODE), state);
                runTag(table.get(Lexing.LEX_CODE).asBlock(),
                       pcOff,
                       buff.get(Lexing.LEX_MEM).asBlock());
                buff.set(Lexing.LEX_LAST_POS, buff.get(Lexing.LEX_CURR_POS));
                buff.set(Lexing.LEX_LAST_ACTION, Value.createFromLong(backtrk));
            } // end if
            if (buff.get(Lexing.LEX_CURR_POS).asLong() >= buff.get(Lexing.LEX_BUFFER_LEN).asLong()) {
                if (buff.get(Lexing.LEX_EOF_REACHED) == Value.FALSE) {
                    return Value.createFromLong(-state - 1);
                } else {
                    c = Lexing.NO_CHAR;
                } // end if/else
            } else {
                final int idx = buff.get(Lexing.LEX_CURR_POS).asLong();
                c = buff.get(Lexing.LEX_BUFFER).asBlock().getUnsignedByte(idx);
                buff.set(Lexing.LEX_CURR_POS, Value.createFromLong(idx + 1));
            } // end if/else
            final int pstate = state;
            if (getShort(table.get(Lexing.LEX_CHECK), base + c) == state) {
                state = getShort(table.get(Lexing.LEX_TRANS), base + c);
            } else {
                state = getShort(table.get(Lexing.LEX_DEFAULT), state);
            } // end if/else
            if (state < 0) {
                buff.set(Lexing.LEX_CURR_POS, buff.get(Lexing.LEX_LAST_POS));
                if (buff.get(Lexing.LEX_LAST_ACTION).asLong() == -1) {
                    Fail.failWith("lexing: empty token");
                } else {
                    return buff.get(Lexing.LEX_LAST_ACTION);
                } // end if/else
            } else {
                final int baseCode = getShort(table.get(Lexing.LEX_BASE_CODE), pstate);
                final int pcOff;
                if (getShort(table.get(Lexing.LEX_CHECK_CODE), baseCode + c) == pstate) {
                    pcOff = getShort(table.get(Lexing.LEX_TRANS_CODE), baseCode + c);
                } else {
                    pcOff = getShort(table.get(Lexing.LEX_DEFAULT_CODE), pstate);
                } // end if/else
                if (pcOff > 0) {
                    runMem(table.get(Lexing.LEX_CODE).asBlock(),
                           pcOff,
                           buff.get(Lexing.LEX_MEM).asBlock(),
                           buff.get(Lexing.LEX_CURR_POS));
                } // end if
                if (c == Lexing.NO_CHAR) {
                    buff.set(Lexing.LEX_EOF_REACHED, Value.FALSE);
                } // end if
            } // end if/else
        } // end while
    } // end method 'caml_new_lex_engine(CodeRunner, Value, Value, Value)'

    /**
     * Reads a short (16-bit integer) from a string/buffer.
     * @param v string/buffer
     * @param n index of value to read
     * @return read short at <tt>(2 * <i>n</i>, 2 * <i>n</i> + 1)</tt> bytes
     */
    static int getShort(final Value v, final int n) {
        final Block b = v.asBlock();
        return b.getUnsignedByte(2 * n) + (b.getByte(2 * n + 1) << 8);
    } // end method 'getShort(Value, int)'

    /**
     * Copies values.
     * @param v where to get copy instruction from
     * @param ofs offset of copy instruction
     * @param mem block where copy should occur
     * @param currPos current position
     */
    private static void runMem(final Block v,
                               final int ofs,
                               final Block mem,
                               final Value currPos) {
        int pc = ofs;
        while (true) {
            final int dst = v.getUnsignedByte(pc++);
            if (dst == Lexing.NO_DEST) {
                return;
            } // end if
            final int src = v.getUnsignedByte(pc++);
            if (src == Lexing.NO_SRC) {
                mem.set(dst, currPos);
            } else {
                mem.set(dst, mem.get(src));
            } // end if/else
        } // end while
    } // end method 'runMem(Value, int, Value, Value)'

    /**
     * Copies values.
     * @param v where to get copy instruction from
     * @param ofs offset of copy instruction
     * @param mem block where copy should occur
     */
    private static void runTag(final Block v,
                               final int ofs,
                               final Block mem) {
        int pc = ofs;
        while (true) {
            final int dst = v.getUnsignedByte(pc++);
            if (dst == Lexing.NO_DEST) {
                return;
            } // end if
            final int src = v.getUnsignedByte(pc++);
            if (src == Lexing.NO_SRC) {
                mem.set(dst, Value.MINUS_ONE);
            } else {
                mem.set(dst, mem.get(src));
            } // end if/else
        } // end while
    } // end method 'runMem(Value, int, Value)'

} // end class 'Lexing'
